#include <game.h>

#if HAVE_CONFIG_H
# include <config.h>
#endif

#include <errors.h>
#include <movegen.h>

#include <assert.h>
#include <stdlib.h>
#include <string.h>


#define INIT_GAME_CAPACITY 256  // game length


typedef struct game_history_entry_t
{
  move_t move;
  int incheck;
  int capture;
  unsigned int castling_caps;
  int epsquare;
  int halfmoves;

  hash_key_t key;
  hash_key_t tag;
  hash_key_t pawn_key;
} game_history_entry_t;


static void make_null_move(game_t* g)
{
  // Collect the information about the move.
  position_t* pos = &(g->pos);
  unsigned int ccaps = position_get_castling_caps(pos);
  int ep = position_get_ep_square(pos);

  // Save information in the game history.
  g->history[g->len].move = NULL_MOVE;
  g->history[g->len].incheck = g->incheck;
  g->history[g->len].capture = EMPTY;
  g->history[g->len].castling_caps = ccaps;
  g->history[g->len].epsquare = ep;
  g->history[g->len].halfmoves = position_get_half_moves(pos);
  g->history[g->len].key = position_get_hash_key(pos);
  g->history[g->len].tag = position_get_hash_tag(pos);
  g->history[g->len].pawn_key = position_get_pawn_hash_key(pos);
  ++g->len;

  g->incheck = 0;
  position_set_ep_square(pos, NO_ENPASSANT);
  position_set_half_moves(pos, 0);

  // Update full moves and change the side to move.
  position_set_full_moves(pos, (position_get_full_moves(pos) + 
				position_get_stm(pos)));
  position_change_stm(pos);
  assert(position_check_hash(pos));
}


static void undo_null_move(game_t* g)
{
  position_t* pos = &(g->pos);
  
  // Change the side to move and restore the full moves.
  position_change_stm(pos);
  position_set_full_moves(pos, (position_get_full_moves(pos) - 
				position_get_stm(pos)));
  
  // Restore the information.
  --g->len;
  int ep = g->history[g->len].epsquare;
  position_set_ep_square(pos, ep);
  int hm = g->history[g->len].halfmoves;
  position_set_half_moves(pos, hm);
  g->incheck = g->history[g->len].incheck;

  assert(position_get_hash_key(pos) == g->history[g->len].key);
  assert(position_get_hash_tag(pos) == g->history[g->len].tag);
  assert(position_get_pawn_hash_key(pos) == g->history[g->len].pawn_key);
  assert(position_check_hash(pos));
}



int game_init(game_t* g)
{
  assert(g != 0);

  position_clear(&g->pos);
  g->incheck = 0;
  g->gamestart = 0;
  g->len = 0;
  g->history_capacity = INIT_GAME_CAPACITY;
  void* mem = malloc(sizeof(game_history_entry_t) * g->history_capacity);
  if (!mem) {
    error_set(ERR_MEMORY);
    return 0;
  }
  g->history = (game_history_entry_t*)mem;

  return 1;
}


int game_copy(const game_t* src, game_t* dst)
{
  if (dst->history_capacity < src->len) {
    size_t sz = sizeof(game_history_entry_t) * src->history_capacity;
    void* mem = realloc(dst->history, sz);
    if (!mem) {
      error_set(ERR_MEMORY);
      return 0;
    }
    dst->history = (game_history_entry_t*)mem;
    dst->history_capacity = src->history_capacity;
    sz = sizeof(game_history_entry_t) * src->len;
  } 
  memcpy(dst->history, src->history, sizeof(game_history_entry_t) * src->len);
  dst->incheck = src->incheck;
  dst->gamestart = src->gamestart;
  dst->len = src->len;
  (dst->pos) = (src->pos);
  return 1;
}


void game_set_init_position(game_t* g)
{
  assert(g != 0);

  const int pieces[16] = {
    ROOK, KNIGHT, BISHOP, QUEEN, KING, BISHOP, KNIGHT, ROOK,
    PAWN, PAWN, PAWN, PAWN, PAWN, PAWN, PAWN, PAWN
  };

  position_t* pos = &(g->pos);
  position_clear(pos);
  g->incheck = 0;
  g->gamestart = 0;
  g->len = 0;
  for (int sq = 0; sq < 16; sq++) {
    position_drop_piece(pos, sq, WHITE, pieces[sq]);
    int rev = SQUARE(FILE(sq), 7 - RANK(sq));
    position_drop_piece(pos, rev, BLACK, pieces[sq]);
  }
  
  if (position_get_stm(pos) != WHITE)
    position_change_stm(pos);

  int caps = WCASTLE_SHORT | WCASTLE_LONG | BCASTLE_SHORT | BCASTLE_LONG;

  position_set_castling_caps(pos, caps);
  position_set_ep_square(pos, NO_ENPASSANT);
  position_set_half_moves(pos, 0);
  position_set_full_moves(pos, 1);
}


int game_set_start_position(game_t* g, const struct position_t* pos)
{
  assert(g != 0);
  assert(pos != 0);
  
  if (!position_verify_legal(pos)) {
    error_set(ERR_ILLEGAL_POSITION);
    return 0;
  }

  g->incheck = (movegen_detect_check(pos) == 0 ? 0 : 1);
  g->len = 0;
  g->pos = *pos;
  g->gamestart = 2 * position_get_full_moves(pos) - 2;
  if (position_get_stm(pos) == BLACK)
    g->gamestart += 1;
  return 1;
}


int game_length(const game_t* g)
{
  assert(g != 0);
  return g->len;
}


int game_moves(const game_t* g, move_t* buf, int buflen)
{
  assert(g != 0);
  assert(buf != 0);
  assert(buflen >= 0);
  
  int n = buflen;
  if (n > g->len)
    n = g->len;

  for (int i = 0; i < n; i++) {
    if (g->history[i].move == NULL_MOVE)
      return i;
    buf[i] = g->history[i].move;
  }

  return n;
}



int game_make_move(game_t* g, move_t m)
{
  assert(g != 0);
  assert(MOVE_GET_EXTRA(m) == 0);

  // Make room for the information.
  if (g->len == g->history_capacity) {
    int newcap = (g->history_capacity == 0
		  ? INIT_GAME_CAPACITY
		  : g->history_capacity * 2);

    void* mem = realloc(g->history,
			sizeof(game_history_entry_t) * newcap);
    if (!mem) {
      error_set(ERR_MEMORY);
      return 0;
    }
    g->history = (game_history_entry_t*)mem;
    g->history_capacity = newcap;
  }

  if (m == NULL_MOVE) {
    make_null_move(g);
    return 1;
  }
  
  // Collect the information about the move.
  position_t* pos = &(g->pos);
  int from = MOVE_FROM(m);
  int to = MOVE_TO(m);
  int promotion = MOVE_PROMOTION(m);
  int capture = position_get_square(pos, to);
  int stm = position_get_stm(pos);
  unsigned int ccaps = position_get_castling_caps(pos);

  // Save information in the game history.
  g->history[g->len].move = m;
  g->history[g->len].incheck = g->incheck;
  g->history[g->len].capture = capture;
  g->history[g->len].castling_caps = ccaps;
  g->history[g->len].epsquare = position_get_ep_square(pos);
  g->history[g->len].halfmoves = position_get_half_moves(pos);
  g->history[g->len].key = position_get_hash_key(pos);
  g->history[g->len].tag = position_get_hash_tag(pos);
  g->history[g->len].pawn_key = position_get_pawn_hash_key(pos);
  ++g->len;
  
  position_set_half_moves(pos, 1 + position_get_half_moves(pos));
  
  // Determine if in check.
  g->incheck = movegen_is_checking(pos, m);    // ??? !!!

  // Remove the captured piece (if any).
  if (capture != EMPTY) {
    position_remove_piece(pos, to);
    position_set_half_moves(pos, 0);
  } 

  // Remove castling capabilities.
  if (from == E1)
    ccaps &= ~(WCASTLE_SHORT | WCASTLE_LONG);
  else if (from == E8)
    ccaps &= ~(BCASTLE_SHORT | BCASTLE_LONG);
  else {
    if (from == H1 || to == H1)
      ccaps &= ~WCASTLE_SHORT;
    if (from == A1 || to == A1)
      ccaps &= ~WCASTLE_LONG;
    if (from == H8 || to == H8)
      ccaps &= ~BCASTLE_SHORT;
    if (from == A8 || to == A8)
      ccaps &= ~BCASTLE_LONG;
  }
  position_set_castling_caps(pos, ccaps);

  // en passant capture
  if (MOVE_ENPASSANT(m)) {
    position_set_half_moves(pos, 0);
    if (stm == WHITE)
      position_remove_piece(pos, to - 8);
    else
      position_remove_piece(pos, to + 8);
  }

  // Move the piece.
  position_move_piece(pos, from, to);
  if (promotion != NO_PROMOTION) {
    position_remove_piece(pos, to);
    position_drop_piece(pos, to, stm, promotion);
  }
  
  // Execute the castlings
  if (MOVE_IS_CASTLING(m)) {
    position_set_half_moves(pos, 0);
    if (m == MOVE_WCASTLE_SHORT)
      position_move_piece(pos, H1, F1);
    else if (m == MOVE_WCASTLE_LONG)
      position_move_piece(pos, A1, D1);
    else if (m == MOVE_BCASTLE_SHORT)
      position_move_piece(pos, H8, F8);
    else if (m == MOVE_BCASTLE_LONG)
      position_move_piece(pos, A8, D8);
  }

  // Set the enpassant square.
  if (BB_TEST(BB_POS_PIECES(pos, stm, PAWN), to)) {
    position_set_half_moves(pos, 0);
    if (to -from == 16) {
      position_set_ep_square(pos, to - 8);
    } else if (to -from == -16) {
      position_set_ep_square(pos, to + 8);
    } else {
      position_set_ep_square(pos, NO_ENPASSANT);
    }
  } else {
    position_set_ep_square(pos, NO_ENPASSANT);
  }

  // Update the sull move counter and change the side to move.
  position_set_full_moves(pos, (position_get_full_moves(pos) + 
				position_get_stm(pos)));
  position_change_stm(pos);
  assert(position_check_hash(pos));
  assert(!(BB_POS_PIECES(pos, WHITE, PAWN) & (BB_RANK_1 | BB_RANK_8)));
  assert(!(BB_POS_PIECES(pos, BLACK, PAWN) & (BB_RANK_1 | BB_RANK_8)));
  return 1;
}


int game_undo_move(game_t* g)
{
  assert(g != 0);
  if (g->len < 1) {
    error_set(ERR_UNDO);
    return 0;
  }

  position_t* pos = &(g->pos);

  // Collect the information about the move.
  move_t m = g->history[g->len - 1].move;
  if (m == NULL_MOVE) {
    undo_null_move(g);
    return 1;
  }

  // Change the side to move and restore the full moves.
  position_change_stm(pos);
  position_set_full_moves(pos, (position_get_full_moves(pos) - 
				position_get_stm(pos)));

  
  int from = MOVE_FROM(m);
  int to = MOVE_TO(m);
  int promotion = MOVE_PROMOTION(m);
  int capture = g->history[g->len - 1].capture;
  int stm = position_get_stm(pos);

  // Move the piece.
  position_move_piece(pos, to, from);
  if (promotion != NO_PROMOTION) {
    position_remove_piece(pos, from);
    position_drop_piece(pos, from, position_get_stm(pos), PAWN);
  }
  
  // en passant capture
  if (MOVE_ENPASSANT(m)) {
    if (stm == WHITE)
      position_drop_piece(pos, to - 8, BLACK, PAWN);
    else
      position_drop_piece(pos, to + 8, WHITE, PAWN);
  }

  // castlings
  if (MOVE_IS_CASTLING(m)) {
    if (m == MOVE_WCASTLE_SHORT)
      position_move_piece(pos, F1, H1);
    else if (m == MOVE_WCASTLE_LONG)
      position_move_piece(pos, D1, A1);
    else if (m == MOVE_BCASTLE_SHORT)
      position_move_piece(pos, F8, H8);
    else if (m == MOVE_BCASTLE_LONG)
      position_move_piece(pos, D8, A8);
  }

  // Replace the captured piece (if any).
  if (capture != EMPTY)
    position_drop_piece(pos, to, 1 - stm, capture);

  // Restore the information in the game history.
  --g->len;
  position_set_castling_caps(pos, g->history[g->len].castling_caps);
  position_set_ep_square(pos, g->history[g->len].epsquare);
  position_set_half_moves(pos, g->history[g->len].halfmoves);
  g->incheck = g->history[g->len].incheck;
  
  assert(position_get_hash_key(pos) == g->history[g->len].key);
  assert(position_get_hash_tag(pos) == g->history[g->len].tag);
  assert(position_get_pawn_hash_key(pos) == g->history[g->len].pawn_key);
  assert(position_check_hash(pos));

  return 1;
}


const struct position_t* game_cur_position(const game_t* g)
{
  assert(g != 0);
  return &(g->pos);
}


void game_cleanup(game_t* g)
{
  if (g != 0)
    free(g->history);
}


int game_repeated_position(const game_t* g)
{
  int hm = position_get_half_moves(&(g->pos));

  // At least four half moves are required to repeat a position.
  if (hm < 4)
    return 0;
  int n = g->len - 3;  // from three slots back in the history
  hm = g->len - hm;
  if (hm < 0)
    hm = 0;
  hash_key_t key = position_get_hash_key(&(g->pos));
  for (int i = hm; i < n; i++) {
    if (g->history[i].key == key)
      if (g->history[i].tag == position_get_hash_tag(&(g->pos)))
	return 1;
  }
  return 0;
}


move_t game_last_move(const game_t* g)
{
  assert(g != 0);
  if (g->len == 0)
    return NULL_MOVE;
  return g->history[g->len - 1].move;
}


int game_last_capture(const game_t* g)
{
  if (g->len == 0)
    return EMPTY;
  return g->history[g->len - 1].capture;
}


int game_in_check(const game_t* g)
{
  assert(g != 0);
  return g->incheck;
}


int game_is_checkmate(const game_t* g)
{
  if (!game_in_check(g))
    return 0;
  move_t moves[MAX_N_MOVES];
  return (movegen_legal_moves(game_cur_position(g), moves) == 0);
}


int game_is_stalemate(const game_t* g)
{
  if (game_in_check(g))
    return 0;
  move_t moves[MAX_N_MOVES];
  return (movegen_legal_moves(game_cur_position(g), moves) == 0);
}


int game_draw_by_material(const game_t* g)
{
  assert(g != 0);

  // KK, KNK, KBK and KBKB with different colors.
  const position_t* pos = &(g->pos);

  int count = BB_POPCOUNT(BB_POS_ALL(pos));
  
  if (count == 2)
    return 1;

  if (count == 3 &&
      (BB_POS_PIECES(pos, WHITE, KNIGHT) != 0 ||
       BB_POS_PIECES(pos, BLACK, KNIGHT) != 0 ||
       BB_POS_PIECES(pos, WHITE, BISHOP) != 0 ||
       BB_POS_PIECES(pos, BLACK, BISHOP) != 0))
    return 1;

  if (count == 4 &&
      (((BB_POS_PIECES(pos, WHITE, BISHOP) & BB_LIGHT_SQUARES) != 0 &&
	(BB_POS_PIECES(pos, BLACK, BISHOP) & BB_LIGHT_SQUARES) != 0) ||
       ((BB_POS_PIECES(pos, WHITE, BISHOP) & BB_DARK_SQUARES) != 0 &&
	(BB_POS_PIECES(pos, BLACK, BISHOP) & BB_DARK_SQUARES) != 0)))
    return 1;
      
  return 0;
}


int game_draw_by_repetition(const game_t* g)
{
  int hm = position_get_half_moves(&(g->pos));
  hash_key_t key = position_get_hash_key(&(g->pos));
  hash_key_t tag = position_get_hash_tag(&(g->pos));

  hm = g->len - hm;
  if (hm < 0)
    hm = 0;

  int reps = 0;
  for (int i = hm; i < g->len; i++) {
    if (g->history[i].key == key)
      if (g->history[i].tag == tag)
	++reps;
  }
  return reps >= 2;
}


int game_draw_by_50(const game_t* g)
{
  assert(g != 0);
  int hm = position_get_half_moves(&(g->pos));
  return hm >= 100;
}


int game_make_checked_move(game_t* g, move_t m)
{
  // TODO:
  // - add promotion, enpassant, castlings information
  // - check that is a legal move
  
  return game_make_move(g, m);
}


int game_undo_checked_move(game_t* g)
{
  // Really needed ?  !!!!!!
  return game_undo_move(g);
}
